//Stephen Kirksharian
//Robert Harkreader
//CPSC 321
//DUE 4/27/08

// Texas A&M University          //
// cpsc321 Computer Architecture //
// $Id: SingleCycleProc.v,v 1.1 2002/04/08 23:16:14 miket Exp miket $ //

// Top Level Architecture Model //

`include "IdealMemory.v"
`include "PC.v"
`include "PC_4.v"
/*-------------------------- CPU -------------------------------
 * This module implements a single-cycle
 * CPU similar to that described in the text book 
 * (for example, see Figure 5.19). 
 *
 */

//
// Input Ports
// -----------
// clock - the system clock (m555 timer).
//
// reset - when asserted by the test module, forces the processor to 
//         perform a "reset" operation.  (note: to reset the processor
//         the reset input must be held asserted across a 
//         negative clock edge).
//   
//         During a reset, the processor loads an externally supplied
//         value into the program counter (see startPC below).
//   
// startPC - during a reset, becomes the new contents of the program counter
//	     (starting address of test program).
// 
// Output Port
// -----------
// dmemOut - contains the data word read out from data memory. This is used
//           by the test module to verify the correctness of program 
//           execution.
//-------------------------------------------------------------------------

`include "32registerFile.v"
`include "mux.v"
`include "main_ctr.v"
`include "lshift2.v"
`include "16_bit_shift.v"
`include "ALU_behav.v"
`include "var_shift.v"
`include "signextend.v"
`include "jump_conc.v"
`include "regfile.v"
`include "PC_adder.v" //for beq/bne offsets
module PipelineSingleCycleProc(CLK, Reset_L, startPC, dmemOut);
	input 	Reset_L, CLK;
	input [31:0] startPC;
	output [31:0] dmemOut;

	wire [31:0] nextPC,PCout,Instr,Data1,Data2,writeData,jumpAddress,jumpCalc,iCalc,immediate,beqPC;
	wire [31:0] toALU,luiToMux,shift_to_mux,ALU_Result,toJumpMux,toJRegisterMux,tempPC,dataToMux;
	wire [4:0]  writeReg;
	wire [3:0]  ALU_ctr;
	wire [2:0]  MemtoReg_ctr;
	wire [1:0]  regDest_ctr,branch_ctr,shift_ctr;
	wire        writeReg_ctr,jump_ctr,memWrite_ctr,sgn_ext_ctr,Zero,not_Zero;
	wire        word_byte_ctr,jump_register_ctr,ALUsrc_ctr,Dummy,Overflow;
	wire        forward,memRead_ctr,globalReset;

	PC pc_cont(Reset_L,startPC,nextPC,CLK,PCout,globalReset);
	PC_4 pcplus4(PCout,tempPC);
	specify
		(PCout*>tempPC)=(1:1:1);
	endspecify

	InstrMem instructions(PCout,Instr);
//	always @(dataToMux) begin
//	  $display($time," PCout=%d\tData1=%d\tData2%d",PCout,Data1,Data2);
//	  //$display($time," nextPC=%d\tjump_register_ctr=%d\tforward=%d\tjump_ctr=%d",nextPC,jump_register_ctr,forward,jump_ctr)	
//	  $display($time," Instr: op=%b\tfunc=%b",Instr[31:26],Instr[5:0]);
//	  $display($time,"       Reg1=%d\tReg2=%d\tDestinationReg: %d",Instr[25:21],Instr[20:16],writeReg);
//	  $display($time,"	   DataMem Out: %d\tmemWrite_ctr: %d\tmemRead_ctr: %d",dataToMux,memWrite_ctr,memRead_ctr);
//	  $display($time,"	   ALU_Result: %d\tData Written to Register: %d",ALU_Result,writeData);
//	end

	always @(PCout) begin
	  $display($time," PCout=%d", PCout);
	end
	always @(Instr) begin
	  $display($time," Instr: op=%b\tfunc=%b",Instr[31:26],Instr[5:0]);
	end
	always @(Data1 or Data2 or Instr) begin
	  $display($time," Data1=%d\tData2=%d\tReg1=%d\tReg2=\t",Data1,Data2,Instr[25:21],Instr[20:16]);
	end
	always @(ALU_Result) begin
	  $display($time," ALU_Result=%d",ALU_Result);
	end
	always @(negedge CLK) begin
	  
	  $display($time," DATA WRITTEN=%d\tWRITTEN TO: %d WROTE?: %d\tMEMTOREG?: %d",writeData,writeReg,writeReg_ctr,MemtoReg_ctr);
	end
	always @(MemtoReg_ctr or writeReg_ctr or jump_ctr or memWrite_ctr or sgn_ext_ctr or word_byte_ctr) begin
	  $display($time," MemtoReg=%d\twriteReg=%d\tword_byte=%d\tmemRead=%d\tmemWrite=%d",MemtoReg_ctr,writeReg_ctr,word_byte_ctr,memRead_ctr,memWrite_ctr);
	end

	main_ctr control(Instr[31:26],Instr[5:0],MemtoReg_ctr,writeReg_ctr,jump_ctr,memWrite_ctr,sgn_ext_ctr,word_byte_ctr,branch_ctr,regDest_ctr,ALU_ctr,jump_register_ctr,ALUsrc_ctr,shift_ctr,memRead_ctr);


	LSHIFT2 jCal({6'b000000,Instr[25:0]},jumpCalc);
	jump_conc conc(PCout[31:28],jumpCalc[27:0],jumpAddress);
	
	//always @(negedge CLK) begin
	//  $display("jumpAdd=%b",jumpAddress);
	//end

	//registerFile regfile(Instr[25:21],Instr[20:16],writeReg,writeData,Data1,Data2,writeReg_ctr,globalReset,CLK);  //ctr->writeReg_ctr
	regFile (Instr[25:21],Instr[20:16],writeReg,writeData,CLK,writeReg_ctr,Reset_L,Data1,Data2);

	SIGN_EXTEND ext(Instr[15:0],iCalc,sgn_ext_ctr);
	specify
		(sgn_ext_ctr*>iCalc)=(1:1:1);
	endspecify

	LSHIFT2 imm(iCalc,immediate);
	PC_adder beq_adder(tempPC,immediate,beqPC);
	specify
		(immediate*>beqPC)=(2:2:2);
	endspecify	
	//always @(negedge CLK) begin
	//  $display("immediate=%b",immediate);
	//end
	
	_16_bit_shift lui(iCalc,luiToMux);
	//always @(luiToMux) begin
	//  $display("luiToMux=%b\tData2=%b",luiToMux,Data2);
	//end

	shift var_shift(Data2,shift_ctr,Instr[10:6],shift_to_mux);
	//always@(shift_to_mux) begin
	//  $display("in=%b\tshamt=%b",Data2,Instr[10:6]);
	//  $display("output=%b",shift_to_mux);
	//end
	
	ALU_behav alu(Data1,toALU,ALU_ctr,ALU_Result,Overflow,1'b0,Dummy,Zero);
	not no(not_Zero,Zero);
specify
	(Data1*>ALU_Result)=(5:5:5);
	(toALU*>ALU_Result)=(5:5:5);
endspecify

	DataMem dmem(ALU_Result,CLK,memRead_ctr,memWrite_ctr,word_byte_ctr,Data2,dataToMux);
	
	//always @(ALU_Result) begin
	//  $display("Result=%d",ALU_Result);
	//  $display("Zero=%b",Zero);
	//  $display("not_Zero=%b",not_Zero);
	//end
	MUX5_3to1 regDest_choose(Instr[20:16],Instr[15:11],{5'b11111},regDest_ctr,writeReg);  //ctr->regDest_ctr out->writeReg->registerFile
	MUX32_2to1 ALUsrc_choose(Data2,iCalc,ALUsrc_ctr,toALU);	//ctr->ALUsrc_ctr out->toALU->BDin of the ALU
	
	//always @(toALU) begin
	//  $display("Data 2=%d\timmediate=%d",Data2,immediate);
	//  $display("ALUsrc_ctr=%d",ALUsrc_ctr);
	//  $display("toALU=%d",toALU);
	//end
	MUX1_3to1 branch(not_Zero,Zero,1'b0,branch_ctr,forward);
	MUX32_2to1 toForward(tempPC,beqPC,forward,toJumpMux);
	//always @(toJumpMux) begin
	//  $display("toJumpMux=%d",toJumpMux);	
	//end
	MUX32_2to1 toJReg(toJumpMux,jumpAddress,jump_ctr,toJRegisterMux);
	
	//always @(toJRegisterMux) begin
	//  $display("toJReg=%d",toJRegisterMux);
	//end
	MUX32_2to1 toPC(toJRegisterMux,Data1,jump_register_ctr,nextPC);
	MUX32_5to1 toReg(ALU_Result,dataToMux,shift_to_mux,tempPC,luiToMux,MemtoReg_ctr,writeData);
specify
	(regDest_ctr*>writeReg)=(2:2:2);
	(ALUsrc_ctr*>toALU)=(2:2:2);
	(branch_ctr*>forward)=(1:1:1);
	(forward*>toJumpMux)=(1:1:1);
	(jump_ctr*>toJRegisterMux)=(1:1:1);
	(jump_register_ctr*>nextPC)=(1:1:1);
	(MemtoReg_ctr*>writeData)=(3:3:3);
endspecify

	
	
//
// Debugging threads that you may find helpful (you may have
// to change the variable names).
//
   /*  Monitor changes in the program counter
   always @(PC)
     #10 $display($time," PC=%d Instr: op=%d rs=%d rt=%d rd=%d imm16=%d funct=%d",
	PC,Instr[31:26],Instr[25:21],Instr[20:16],Instr[15:11],Instr[15:0],Instr[5:0]);
   */

   /*   Monitors memory writes
   always @(MemWrite)
	begin
	#1 $display($time," MemWrite=%b clock=%d addr=%d data=%d", 
	            MemWrite, clock, dmemaddr, rportb);
	end
   */
   
endmodule // CPU


module m555 (CLK);
   parameter StartTime = 0, Ton = 100, Toff = 100, Tcc = Ton+Toff; // 
 
   output CLK;
   reg 	  CLK;
   
   initial begin
      #StartTime CLK = 0;
   end
   
   // The following is correct if clock starts at LOW level at StartTime //
   always begin
      #Toff CLK = ~CLK;
      #Ton CLK = ~CLK;
   end
endmodule

   
module testCPU(Reset_L, startPC, testData);
   input [31:0] testData;
   output 	Reset_L;
   output [31:0] startPC;
   reg 		 Reset_L;
   reg [31:0] 	 startPC;
   
   initial begin
      // Your program 1
      Reset_L = 0;  startPC = 0 * 4;
      #1001 // insures reset is asserted across negative clock edge
	 Reset_L = 1; 
      #10000; // allow enough time for program 1 to run to completion
      Reset_L = 0;
      #1 $display ("Program 1: Result: %d", testData);
      
      // Your program 2
      startPC = 14 * 4;
      #101 Reset_L = 1; 
      #10000;
      Reset_L = 0;

      #1 $display ("Program 2: Result: %d", testData);
      
      // etc.
      // Run other programs here
      
      
      $finish;
   end endmodule // testCPU

module TopProcessor;
   wire reset, CLK, Reset_L;
   wire [31:0] startPC;
   wire [31:0] testData;
   
   m555 system_clock(CLK);
   PipelineSingleCycleProc SSProc(CLK, Reset_L, startPC, testData);
   testCPU tcpu(Reset_L, startPC, testData); 

endmodule // TopProcessor
